---
id: skeleton
title: Skeleton
sidebar_label: Overview
---

Moti's skeleton component is great for showing animated loading states. It works on all platforms, including web.

```tsx
import React from 'react'
import { Skeleton } from 'moti/skeleton'

const Loader = ({ children }) => <Skeleton>{children}</Skeleton>

export default Loader
```

- [Video](https://twitter.com/FernandoTheRojo/status/1366902697010622466)
- [TypeScript usage](https://twitter.com/FernandoTheRojo/status/1366912660273590273?s=20)
- [Inspiration](https://twitter.com/FernandoTheRojo/status/1366903666578489346?s=20)

## Example

<iframe
  src="https://stackblitz.com/edit/nextjs-g2ae39?embed=1&file=pages/index.tsx"
  className="stackblitz"
/>

## Install

### Expo Users

You'll also want to install `expo-linear-gradient`.

_Please note that you must have Reanimated 3 installed. See [installation](/installation) steps for more info._

```bash
expo install expo-linear-gradient
```

### Non-Expo users

If you aren't using Expo, you can either use the `expo-linear-gradient` package, or `react-native-linear-gradient`.

#### Option 1: `expo-linear-gradient`

```bash npm2yarn
npm install expo-linear-gradient
```

Keep in mind you'll need to also install `expo-modules-core`. Please make sure you complete any installation steps required for Expo's [linear gradient](https://docs.expo.io/versions/latest/sdk/linear-gradient/) component.

#### Option 2: `react-native-linear-gradient`

<details>
<summary><strong>Click here to use <code>react-native-linear-gradient</code></strong></summary>

First, install the module resolver plugin:

```npm2yarn
npm install babel-plugin-module-resolver
```

Next, add this to your app's `babel.config.js`'s `plugins` array.

```js
// babel.config.js
module.exports = function (api) {
  api.cache(true)
  return {
    plugins: [
      [
        'module-resolver',
        {
          root: ['./'],
          alias: {
            'moti/skeleton': 'moti/skeleton/react-native-linear-gradient',
          },
        },
      ],
    ],
  }
}
```

This assumes you've installed `react-native-linear-gradient` and followed any of its installation requirements.

Your skeletons will now use `react-native-linear-gradient`. You can continue importing from `moti/skeleton`.

</details>

## Video

<div
  style={{
    position: 'relative',
    paddingBottom: '209.59302325581396%',
    height: 0,
    width: '100%',
  }}
>
  <iframe
    src="https://www.loom.com/embed/f2d5ff02cfec46bc8795f46965ae9b52"
    frameborder="0"
    webkitallowfullscreen
    mozallowfullscreen
    allowfullscreen
    style={{
      position: 'absolute',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      width: '100%',
      height: '100%',
    }}
  ></iframe>
</div>

## Usage

### Show/hide

Skeleton will hide when data exists by default.

```tsx
<Skeleton>{!!data ? <Data /> : null}</Skeleton>
```

You can always `show` the skeleton:

```tsx
<Skeleton show={loading}>
  <Data />
</Skeleton>
```

Or hide it:

```tsx
<Skeleton show={false}>{!!data ? <Data /> : null}</Skeleton>
```

### Border radius

Use `radius` to show a circle, square, or custom border radius. Defaults to `8`.

### Circle

```tsx
<Skeleton height={48} width={48} radius="round">
  {!!data ? <Data /> : null}
</Skeleton>
```

### Square

```tsx
<Skeleton height={48} width={48} radius="square">
  {!!data ? <Data /> : null}
</Skeleton>
```

### Custom radius

```tsx
<Skeleton radius={16}>{!!data ? <Data /> : null}</Skeleton>
```

### Color modes

`light` or `dark`

```tsx
<Skeleton colorMode="light" />
```

### Custom animation delay

```tsx
<Skeleton delay={250} />
```

### Custom animation transition

```tsx
<Skeleton
  transition={{
    translateX: {
      // defaults to a 3000ms timing function
      type: 'spring',
    },
  }}
/>
```

## Full example

Here's the code from the video above:

```tsx
import React, { useReducer } from 'react'
import { StyleSheet, Pressable } from 'react-native'
import { MotiView } from 'moti'
import { Skeleton } from 'moti/skeleton'

const Spacer = ({ height = 16 }) => <MotiView style={{ height }} />

export default function HelloWorld() {
  const [dark, toggle] = useReducer((s) => !s, true)

  const colorMode = dark ? 'dark' : 'light'

  return (
    <Pressable onPress={toggle} style={styles.container}>
      <MotiView
        transition={{
          type: 'timing',
        }}
        style={[styles.container, styles.padded]}
        animate={{ backgroundColor: dark ? '#000000' : '#ffffff' }}
      >
        <Skeleton colorMode={colorMode} radius="round" height={75} width={75} />
        <Spacer />
        <Skeleton colorMode={colorMode} width={250} />
        <Spacer height={8} />
        <Skeleton colorMode={colorMode} width={'100%'} />
        <Spacer height={8} />
        <Skeleton colorMode={colorMode} width={'100%'} />
      </MotiView>
    </Pressable>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  padded: {
    padding: 16,
  },
})
```

## `<Skeleton.Group />`

If you have many skeleton components, you can now wrap them with a single `<Skeleton.Group show={loading} />` component. This will help you achieve this [type of effect](https://twitter.com/pacocoursey/status/1428057307670323202).

```tsx
import { Image, Text } from 'react-native'
import { Skeleton } from 'moti/skeleton'

export function ListItem({ loading, item }) {
  return (
    <Skeleton.Group show={loading}>
      <Skeleton>
        <Image src={{ uri: image.avatar }} />
      </Skeleton>
      <Skeleton>
        <Text>{item.title || ' '}</Text>
      </Skeleton>
    </Skeleton.Group>
  )
}
```
